#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   networkEvolutionObjectModel.py
@Contact :   zhujuxing@foxmail.com
@License :   (C)Copyright 2021-2022

@Modify Time      @Author    @Version    @Description
------------      -------    --------    -----------
2021/6/16 12:53   zhujuxing      1.0         None
"""

import pickle
from enum import Enum

import networkx as nx
import pandas as pd

from cloudVirtrualizedNetworkApplicationReliability.src.networkEvolutionConditionGenerate import \
    generate_network_evolution_condition


def application_info_initial(application_info):
    # application_info["ApplicationVNFs"] =
    application_info['State'] = 1
    application_info['Traffic'] = 1.0
    # application_info['FailStartTime'] = 0
    application_info['WeightedUnavailTime'] = 0.0
    application_info['Availability'] = 1.0
    application_info = application_info.set_index('ApplicationID')
    return application_info


def vnf_info_initial(application_info, vnf_info):
    vnf_info['VNFDeployNode'] = vnf_info['VNFDeployNode'].apply(str.split, args=[','])
    vnf_info['VNFBackupNode'] = vnf_info['VNFBackupNode'].apply(lambda x: [x] if type(x) == str else x)
    vnf_info['State'] = 1
    vnf_info['Traffic'] = 1.0
    vnf_info['Wait'] = vnf_info.apply(lambda x: set(), axis=1)
    # vnf_info['FailStartTime'] = 0.0
    vnf_info['ApplicationDeployed'] = vnf_info.apply(lambda x: set(), axis=1)
    for i in application_info.index:
        for j in application_info.loc[i, "ApplicationVNFs"]:
            vnf_info_app_deployed_set = vnf_info.loc[vnf_info.VNFID == j].iloc[0]['ApplicationDeployed']
            vnf_info_app_deployed_set.add(application_info.loc[i, "ApplicationID"])
    vnf_info['State'] = vnf_info.apply(
        lambda x: {c: 1 for c in x['VNFDeployNode']}, axis=1)
    vnf_info['Traffic'] = vnf_info.apply(
        lambda x: {c: (1 / len(x["VNFDeployNode"])) for c in x['VNFDeployNode']}, axis=1)
    vnf_info = vnf_info.set_index("VNFID")
    return vnf_info


def component_info_initial(edge_info, node_info, vlink_info, vnf_info):
    components_info = pd.DataFrame(columns=['ID', 'Type', 'Idle', 'State', 'VNF', 'Vlink', 'DataPlane', 'ControlPlane'])
    components_info['ID'] = node_info['NodeID'].to_list().copy() + edge_info['EdgeID'].to_list().copy()
    components_info['Type'] = node_info['Type'].tolist().copy() + edge_info['Type'].to_list().copy()
    components_info['Idle'] = node_info['NodeFunction'].apply(lambda x: 1 if x == '空闲' else 0).to_list().copy() + [
        0] * len(edge_info)
    components_info['State'] = 1
    components_info['VNF'] = [set() for i in range(len(components_info))]
    components_info['Vlink'] = [{} for i in range(len(components_info))]
    components_plane = pd.Series(node_info['NodePlane'].to_list().copy() + edge_info['EdgePlane'].to_list().copy())
    components_info['DataPlane'] = components_plane.apply(lambda x: True if '数据' in x else False)
    components_info['ControlPlane'] = components_plane.apply(lambda x: True if '控制' in x else False)
    components_info = components_info.set_index("ID")
    for vnf in vnf_info.index:
        for node in vnf_info.loc[vnf, 'VNFDeployNode']:
            components_info.loc[node, 'VNF'].add(vnf + "_主")
        backup_node = vnf_info.loc[vnf, 'VNFBackupNode']
        if type(backup_node) == str:
            components_info.loc[backup_node, 'VNF'].add(vnf + "_备")
    for vlink in vlink_info.index:
        physical_link_of_vlink = vlink_info.loc[vlink, "VlinkPhysicalComponents"]
        for k in physical_link_of_vlink.keys():
            for component in physical_link_of_vlink[k]:
                if vlink in components_info.loc[component, "Vlink"].keys():
                    components_info.loc[component, "Vlink"][vlink].add(k)
                else:
                    components_info.loc[component, "Vlink"][vlink] = {k}

    return components_info


def select_vlink_by_vnf(graph, nodes_vnf_in, nodes_vnf_out, edf, state, traffic, vlink_of_physical_path):
    for nin, nout in pairs(nodes_vnf_in, nodes_vnf_out):
        try:
            path = nx.shortest_path(graph, nin, nout)
        except Exception as e:
            raise Exception("在寻找节点%s到%s的控制链路是，出现异常%s" % (nin, nout, str(e)))
        path = components_in_graph_path(edf, path)
        vlink_of_physical_path[(nin, nout)] = path
        state[(nin, nout)] = 1
        traffic[(nin, nout)] = 1 / len(list(pairs(nodes_vnf_in, nodes_vnf_out)))
    return state, traffic, vlink_of_physical_path


def pairs(list1, list2):
    for i in list1:
        for j in list2:
            yield i, j


def components_in_graph_path(edf, path):
    nds = path
    components = []
    for i in range(len(nds) - 1):
        u = nds[i]
        v = nds[i + 1]
        eg = get_edge_id(u, v, edf)
        components.append(nds[i])
        components.append(eg)
    components = components[1:]
    return components


def get_edge_id(u: str, v: str, edf: pd.DataFrame) -> str:
    eg_df = edf.loc[(edf.EdgeSourceNode == u) & (edf.EdgeDestinationNode == v) | (edf.EdgeSourceNode == v) & (
            edf.EdgeDestinationNode == u)]
    eg = eg_df.iloc[0]['EdgeID']
    return eg


def vlink_info_initial(application_info, vnf_info, edge_info, data_plane, control_plane, control_plane_type):
    # 增加vlink的数据链路信息
    vlink_info = pd.DataFrame(
        columns=["VlinkSourceVNF", "VlinkDestinationVNF", "DataType", "VlinkPhysicalComponents", "State", "Traffic",
                 "ApplicationDeployed"])
    vlink_of_vnf_set = set()
    for i in application_info.index:
        application_vnfs = application_info.loc[i, "ApplicationVNFs"]
        for j in range(len(application_vnfs) - 1):
            vnf_in = application_vnfs[j]
            vnf_out = application_vnfs[j + 1]
            vlink_of_vnf_set.add((vnf_in, vnf_out))
            nodes_vnf_in = vnf_info.loc[vnf_in, "VNFDeployNode"]
            nodes_vnf_out = vnf_info.loc[vnf_out, "VNFDeployNode"]
            state, traffic, vlink_of_physical_path = select_vlink_by_vnf(data_plane, nodes_vnf_in,
                                                                         nodes_vnf_out, edge_info, {}, {}, {})
            vlink_info = vlink_info.append(pd.DataFrame(
                [[vnf_in, vnf_out, "数据", vlink_of_physical_path, state, traffic, i]],
                columns=["VlinkSourceVNF", "VlinkDestinationVNF", "DataType", "VlinkPhysicalComponents", "State",
                         "Traffic", "ApplicationDeployed"]))

    def agg_application_deployed(x):
        apps = set()
        for app in x['ApplicationDeployed']:
            apps.add(app)
        df = x.iloc[0]
        df['ApplicationDeployed'] = apps
        return df

    vlink_info = vlink_info.groupby(['VlinkSourceVNF', 'VlinkDestinationVNF']).apply(agg_application_deployed)
    if control_plane_type != ControlPlaneType.NONE:
        # 增加vlink的控制链路信息
        vnf_nce = vnf_info.loc[vnf_info.VNFDataType == "NCE"].index.to_list()
        vnf_ms_data = vnf_info.loc[(vnf_info.VNFBackupType == "主备") & (vnf_info.VNFDataType == "数据")].index.to_list()
        vnf_ms_control = vnf_info.loc[(vnf_info.VNFBackupType == "主备") & (vnf_info.VNFDataType == "DCGW控制")] \
            .index.to_list()
        vnf_ms = vnf_ms_data
        vnf_ms.extend(vnf_ms_control)
        for vnf_in, vnf_out in pairs(vnf_ms, vnf_nce):
            node_deploy = vnf_info.loc[vnf_in, "VNFDeployNode"]
            node_backup = vnf_info.loc[vnf_in, "VNFBackupNode"]
            node_vnf_nce = vnf_info.loc[vnf_out, "VNFDeployNode"]
            state = {}
            traffic = {}
            vlink_of_physical_path = {}
            state, traffic, vlink_of_physical_path = select_vlink_by_vnf(control_plane, node_deploy, node_vnf_nce,
                                                                         edge_info, state, traffic,
                                                                         vlink_of_physical_path)
            state, traffic, vlink_of_physical_path = select_vlink_by_vnf(control_plane, node_backup, node_vnf_nce,
                                                                         edge_info, state, traffic,
                                                                         vlink_of_physical_path)
            vlink_info = vlink_info.append(pd.DataFrame(
                [[vnf_in, vnf_out, "控制", vlink_of_physical_path, state, traffic, set()]],
                columns=["VlinkSourceVNF", "VlinkDestinationVNF", "DataType", "VlinkPhysicalComponents", "State",
                         "Traffic", "ApplicationDeployed"]))

        vnf_nway = vnf_info.loc[(vnf_info.VNFBackupType == "N way") & (vnf_info.VNFDataType != "NCE")].index.to_list()
        vlink_info = vlink_info_control_add(edge_info, vlink_info, vnf_info, vnf_nway, vnf_nce, control_plane)

        vnf_dcgw = vnf_info.loc[vnf_info.VNFDataType == 'DCGW控制'].index.to_list()
        vnf_data = vnf_info.loc[vnf_info.VNFDataType == '数据'].index.to_list()
        if control_plane_type == ControlPlaneType.CENTRALIZATION:
            vlink_info = vlink_info_control_add(edge_info, vlink_info, vnf_info, vnf_nce, vnf_dcgw,
                                                control_plane)
        elif control_plane_type == ControlPlaneType.DISTRIBUTION:
            vlink_info = vlink_info_control_add(edge_info, vlink_info, vnf_info, vnf_data, vnf_dcgw,
                                                data_plane)
        vlink_info["VlinkID"] = ["Vlink%d" % (i + 1) for i in range(len(vlink_info))]
        vlink_info = vlink_info.set_index("VlinkID")
        vlink_info = vlink_info[["VlinkSourceVNF", "VlinkDestinationVNF", "DataType", "VlinkPhysicalComponents",
                                 "State", "ApplicationDeployed"]]
    else:
        pass

    return vlink_info


def vlink_info_control_add(edge_info, vlink_info, vnf_info, vnf_pre, vnf_sub, graph):
    for vnf_in, vnf_out in pairs(vnf_pre, vnf_sub):
        nodes_vnf_in = vnf_info.loc[vnf_in, "VNFDeployNode"]
        nodes_vnf_out = vnf_info.loc[vnf_out, "VNFDeployNode"]
        state, traffic, vlink_of_physical_path = select_vlink_by_vnf(graph, nodes_vnf_in,
                                                                     nodes_vnf_out, edge_info, {}, {}, {})
        vlink_info = vlink_info.append(pd.DataFrame(
            [[vnf_in, vnf_out, "控制", vlink_of_physical_path, state, traffic, set()]],
            columns=["VlinkSourceVNF", "VlinkDestinationVNF", "DataType", "VlinkPhysicalComponents", "State",
                     "Traffic", "ApplicationDeployed"]))
    return vlink_info


class ControlPlaneType(Enum):
    DISTRIBUTION = 1
    CENTRALIZATION = 2
    NONE = 3


class CloudVirtualizedNetwork:
    """
    云化虚拟化网络类


    """
    node_info = pd.DataFrame()
    edge_info = pd.DataFrame()
    fail_info = pd.DataFrame()
    components_info = pd.DataFrame()
    vnf_info = pd.DataFrame()
    vlink_info = pd.DataFrame()
    application_info = pd.DataFrame()

    data_plane = nx.Graph()
    control_plane = nx.Graph()
    application_graph = nx.DiGraph()

    control_plane_type = ControlPlaneType.CENTRALIZATION

    def __init__(self, file: str, control_plane_type=ControlPlaneType.CENTRALIZATION):
        """
        该函数为演化对象生成函数。
        :param file
            输入参数文件
        :param control_plane_type
            控制平面类型，枚举类
        """
        self.control_plane_type = control_plane_type

        node_info = pd.read_excel(file, sheet_name='node_info')
        edge_info = pd.read_excel(file, sheet_name='edge_info')
        fail_info = pd.read_excel(file, sheet_name='fail_info')
        vnf_info = pd.read_excel(file, sheet_name='vnf_info')
        application_info = pd.read_excel(file, sheet_name=' application_info')

        # 在数据面和控制面添加节点和边
        nodes_in_data_plane = []
        nodes_in_control_plane = []
        for i in node_info.iterrows():
            if '数据' in i[1]['节点所属平面']:
                nodes_in_data_plane.append(i[1]['节点ID'])
            if '控制' in i[1]['节点所属平面']:
                nodes_in_control_plane.append(i[1]['节点ID'])

        edges_in_data_plane = []
        edges_in_control_plane = []
        for i in edge_info.iterrows():
            if '数据' in i[1]['链路所属平面']:
                edges_in_data_plane.append((i[1]['源节点ID'], i[1]['目的节点ID'], i[1]["权重"]))
            if '控制' in i[1]['链路所属平面']:
                edges_in_control_plane.append((i[1]['源节点ID'], i[1]['目的节点ID'], i[1]["权重"]))

        self.data_plane.add_nodes_from(nodes_in_data_plane)
        self.data_plane.add_weighted_edges_from(edges_in_data_plane)
        self.control_plane.add_nodes_from(nodes_in_control_plane)
        self.control_plane.add_weighted_edges_from(edges_in_control_plane)

        node_info = node_info.rename(columns={"节点ID": 'NodeID',
                                              "节点类型": "Type",
                                              "节点功能": "NodeFunction",
                                              "节点所属平面": "NodePlane"
                                              })
        edge_info = edge_info.rename(columns={"链路ID": 'EdgeID',
                                              "源节点ID": 'EdgeSourceNode',
                                              "目的节点ID": 'EdgeDestinationNode',
                                              "链路类型": 'Type',
                                              "链路所属平面": 'EdgePlane',
                                              "权重": "EdgeWeight"
                                              })
        fail_info = fail_info.rename(columns={'类型': 'Type',
                                              '故障模式': 'FailType',
                                              '平均故障间隔时间': 'MTBF',
                                              '故障时间分布': 'FailDistri',
                                              '故障检测率': 'FDR',
                                              '故障检测时间': 'FDT',
                                              '自动维修概率': 'FARR',
                                              '自动维修时间': 'FART',
                                              '平均人工维修时间': 'MTTR',
                                              '维修时间分布': 'RecoDistri',
                                              '倒换或迁移成功率': 'MSR',
                                              '倒换或迁移时间': 'MST'
                                              })
        vnf_info = vnf_info.rename(columns={'VNF名称': 'VNFID',
                                            '数据类型': 'VNFDataType',
                                            '备份类型': 'VNFBackupType',
                                            '工作节点': 'VNFDeployNode',
                                            '备用节点': 'VNFBackupNode',
                                            '倒换概率': 'VNFFailSR',
                                            # '倒换时间': 'VNFFailST',
                                            # '倒换控制链路': 'VNFSwitchPath'
                                            })
        application_info = application_info.rename(columns={'业务名称': 'ApplicationID',
                                                            '业务逻辑路径': 'ApplicationVNFs',
                                                            '流量阈值': 'TrafficThreshold'
                                                            })
        application_info["ApplicationVNFs"] = application_info["ApplicationVNFs"].apply(str.split, args=[','])
        vnf_transmit = vnf_info.loc[vnf_info.VNFDataType == "DCGW转发"]["VNFID"].to_list()[0]

        def modify_application_vnf(app_vnf_list, vnf: str):
            app_vnf_list.insert(1, vnf)
            app_vnf_list.insert(-1, vnf)
            return app_vnf_list

        application_info["ApplicationVNFs"] = application_info["ApplicationVNFs"].apply(modify_application_vnf,
                                                                                        args=[vnf_transmit])

        application_graph = nx.DiGraph()
        for i in application_info["ApplicationVNFs"].to_list():
            application_graph.add_edges_from(nx.path_graph(i, create_using=nx.DiGraph).edges)

        self.application_graph = application_graph

        self.node_info = node_info
        self.edge_info = edge_info
        self.fail_info = fail_info

        vnf_info = vnf_info_initial(application_info, vnf_info)

        application_info = application_info_initial(application_info)

        vlink_info = vlink_info_initial(application_info, vnf_info, edge_info, self.data_plane, self.control_plane,
                                        self.control_plane_type)

        components_info = component_info_initial(edge_info, node_info, vlink_info, vnf_info)

        self.components_info = components_info
        self.vnf_info = vnf_info
        self.vlink_info = vlink_info
        self.application_info = application_info
        # print("网络对象已生成")

    def __str__(self):
        item = [self.components_info, self.vnf_info, self.vlink_info, self.application_info]
        string = ""
        for i in item:
            string += "------------------------------------------------\n"
            string += str(i) + "\n"
        return string

    # def __del__(self):
    # print("网络对象已销毁")

    def save_current_state(self, path):
        writer = pd.ExcelWriter(path)
        self.components_info.to_excel(writer, sheet_name='components_info')
        self.vnf_info.to_excel(writer, sheet_name='vnf_info')
        self.vlink_info.to_excel(writer, sheet_name='vlink_info')
        self.application_info.to_excel(writer, sheet_name='application_info')
        writer.save()

    def route_alorithm(self, source, target, plan, edf):
        nil = True
        if plan in ("Control", "控制"):
            path = nx.shortest_path(self.control_plane, source, target, "weight")
            path_weight = nx.shortest_path_length(self.control_plane, source, target, "weight")
        elif plan in ("Data", "数据"):
            path = nx.shortest_path(self.data_plane, source, target, "weight")
            path_weight = nx.shortest_path_length(self.data_plane, source, target, "weight")
        else:
            raise Exception("该平面未定义")
        if path_weight == float('inf'):
            nil = False
        path = components_in_graph_path(edf, path)
        return path, nil

    def update_vlink_of_component(self, ns, nt, path_new, path_old, vlink):
        for i in path_old:
            self.components_info.loc[i, "Vlink"][vlink].remove((ns, nt))
            if not self.components_info.loc[i, "Vlink"][vlink]:
                del self.components_info.loc[i, "Vlink"][vlink]
        for i in path_new:
            if vlink in self.components_info.loc[i, "Vlink"]:
                self.components_info.loc[i, "Vlink"][vlink].add((ns, nt))
            else:
                self.components_info.loc[i, "Vlink"][vlink] = {(ns, nt)}

    # def update_vlink_of_component_migration(network, ):

    def to_pickle(self, filename):
        file = open(filename, 'wb')
        pickle.dump(self, file)
        file.close()

    def evol(self, t):
        evol = generate_network_evolution_condition(self, t)
        return evol


# --------------------------测试函数区-----------------------------------
def test():
    file = r"../inputFiles/case_4server_2link_backup.xlsx"
    # file = "../inputFiles/case_128server_2link_backup.xlsx"
    # graph = CloudVirtualizedNetwork(file, control_plane_type=ControlPlaneType.DISTRIBUTION)
    graph = CloudVirtualizedNetwork(file, control_plane_type=ControlPlaneType.CENTRALIZATION)
    # print(network)
    return graph


def test_pairs():
    print(list(pairs([1, 2], [3, 4, 5])))


def test_components_in_graph_path():
    graph = test()
    path = nx.shortest_path(graph, 'D1', 'V1')
    components = components_in_graph_path(graph, path)
    print(components)


def test_reroute_vnf():
    graph = test()
    graph.data_plane.edges[("S1", "Vs1")]['weight'] = float('inf')
    graph.control_plane.edges[("S1", "Vs1")]['weight'] = float('inf')
    return graph


if __name__ == '__main__':
    g = test()
    # network.save_current_state(r"..\data_in\network.xlsx")
    # test_pairs()
    # test_components_in_graph_path()
    # g = test_reroute_vnf()
